/*
    Author: Christopher Diggins
    License: MIT Licence 1.0
    Website: http://www.cdiggins.com
*/

#ifndef LLS_TO_LUA_HPP
#define LLS_TO_LUA_HPP

namespace lls
{
	struct LLSchemeToLua
	{
		LuaMachine& L;
		bool bPreprocess;

		LLSchemeToLua(LuaMachine& l, bool preprocess) 
			: L(l), bPreprocess(preprocess)
		{ }

		String LineComment(ConstString s) {
			return "-- " + s + "\n";
		}

		void PostfixUnaryPrimToLua(Primitive* x, ConstString s) {
			L += "Unmarshall(";
			ExprToLua(x->GetChild(0));
			L += " ";
			L += s;
			L += ")";
		}

		void PrefixUnaryPrimToLua(Primitive* x, ConstString s) {
			L += "Unmarshall(";
			L += s;		
			ExprToLua(x->GetChild(0));
			L += ")";
		}

		void BinaryPrimToLua(Primitive* x, ConstString s) {
			L += "Marshall(Unmarshall(";
			ExprToLua(x->GetChild(0));
			L += ") ";
			L += s;
			L += " Unmarshall(";
			ExprToLua(x->GetChild(1));
			L += "))";
		}

		void PrimitiveToLua(Primitive* x) {
			switch (x->Id()) 
			{
			case prim_inc: PostfixUnaryPrimToLua(x, "++"); break;
			case prim_dec: PostfixUnaryPrimToLua(x, "--"); break; 
			case prim_neg: PrefixUnaryPrimToLua(x, "-"); break;
			case prim_add: BinaryPrimToLua(x, " + "); break;
			case prim_sub: BinaryPrimToLua(x, " - "); break;
			case prim_mul: BinaryPrimToLua(x, " * "); break;
			case prim_div: BinaryPrimToLua(x, " / "); break;
			case prim_mod: BinaryPrimToLua(x, " % "); break;
			case prim_gt: BinaryPrimToLua(x, " > "); break;
			case prim_lt: BinaryPrimToLua(x, " < "); break;
			case prim_gteq: BinaryPrimToLua(x, " >= "); break;
			case prim_lteq: BinaryPrimToLua(x, " <= "); break;
			case prim_eq: BinaryPrimToLua(x, " == "); break;
			case prim_neq: BinaryPrimToLua(x, " ~= "); break;
			case prim_and: BinaryPrimToLua(x, " and "); break;
			case prim_or: BinaryPrimToLua(x, " or "); break;
			case prim_xor: Error("not implemented yet"); break;
			case prim_cond: Error("not implemented yet"); break;
			case prim_null: L += "{}"; break;
			case prim_nil: L += "{}"; break;
			case prim_unused: Error("unused primitive");  break;
			default: Error("Unrecognized enumeration " + ToStr(x->Id())); break;
			}
		}
		
		void ArgsToLua(Args* x) {
			L += "(";
			for (int i=0; i < x->GetNumChildren(); ++i) {
				if (i != 0) L += ", ";
				ExprToLua(x->GetChild(i));
			}
			L += ")";
		}

		void TypeToLua(Type* x) {
			// TODO: handle parameterized types.
			L += x->Label();
		}

		void LiteralToLua(Literal* x) {			
			if (x->Is<PLiteral<int> >()) {
				int t = x->As<PLiteral<int> >()->Param();
				L += "Int(";
				L += ToStr(t);
				L += ")";
			}
			else if (x->Is<PLiteral<bool> >()) {
				bool t = x->As<PLiteral<bool> >()->Param();
				L += "Bool(";
				L += ToStr(t);
				L += ")";
			}
			else if (x->Is<PLiteral<char> >()) {
				char t = x->As<PLiteral<char> >()->Param();
				// TODO: fix. I am pretty sure that Lua doesn't support characters
				L += "Char('";
				L += EscapeStr(ToStr(t));
				L += "')";
			}
			else if (x->Is<PLiteral<PChar> >()) {
				PChar t = x->As<PLiteral<PChar> >()->Param();
				L += "String(\"";
				L += EscapeStr(t);
				L += "\")";
			}
			else if (x->Is<PLiteral<String> >()) {
				String t = x->As<PLiteral<String> >()->Param();
				L += "String(\"";
				L += EscapeStr(t);
				L += "\")";
			}
			else if (x->Is<PLiteral<double> >()) {
				double t = x->As<PLiteral<double> >()->Param();
				L += "Float(";
				L += ToStr(t);
				L += ")";
			}
			else {
				Error("unhandled literal type");
			}
		}

		void ExprToLua(SExpr* x) {
			if (x->Is<Primitive>()) {
				PrimitiveToLua(x->As<Primitive>());
			}
			else if (x->Is<Name>()) {
				String s = x->As<Name>()->Label();
				L += s;
			}
			else if (x->Is<Get>()) {
				Get* t = x->As<Get>();
				String s = t->GetNameStr();
				L += s;
			}
			else if (x->Is<Set>()) {
				Set* t = x->As<Set>();
				String s = t->GetNameStr();
				L += s;
				L += " = ";
				ExprToLua(t->GetVal());
			}
			else if (x->Is<Invoke>()) {
				Invoke* t = x->As<Invoke>();				
				ExprToLua(t->Object());
				L += ":";
				String s = t->GetNameStr();
				L += s;
				ArgsToLua(t->GetArgs());
			}
			else if (x->Is<Apply>()) {
				Apply* t = x->As<Apply>();				
				ExprToLua(t->Func());
				ArgsToLua(t->GetArgs());
			}
			else if (x->Is<Literal>()) {
				LiteralToLua(x->As<Literal>());
			}
			else if (x->Is<GetAt>()) {
				GetAt* t = x->As<GetAt>();
				ExprToLua(t->Collection());
				L += "[";
				ExprToLua(t->Index());
				L += "]";
			}
			else if (x->Is<SetAt>()) {
				SetAt* t = x->As<SetAt>();
				ExprToLua(t->Collection());
				L += "[";
				ExprToLua(t->Index());
				L += "] = ";
				ExprToLua(t->GetVal());
			}
			else if (x->Is<GetField>()) {
				GetField* t = x->As<GetField>();
				ExprToLua(t->Object());
				L += ".";
				String s = t->GetNameStr();
				L += s;
			}
			else if (x->Is<SetField>()) {
				SetField* t = x->As<SetField>();
				ExprToLua(t->Object());
				L += ".";
				String s = t->GetNameStr();
				L += s;
				L += " = ";
				ExprToLua(t->GetVal());
			}
			else if (x->Is<New>()) {
				New* t = x->As<New>();
				TypeToLua(t->GetType());
				L += ".New";
				ArgsToLua(t->GetArgs());
			}
			else {
				Error("cannot convert s-expression to Lua");
			}
		}

		void StatementToLua(SExpr* x) {
			if (x->Is<If>()) {
				If* t = x->As<If>();	
				L += "if Unmarshall(";
				ExprToLua(t->Cond());
				L += ") then\n";
				StatementToLua(t->TrueBranch());
				if (t->FalseBranch() != NULL) {
					L += "else\n";
					StatementToLua(t->FalseBranch());
				}
				L += "end\n";
			}
			else if (x->Is<While>()) {
				While* t = new While();				
				L += "while ";
				ExprToLua(t->Cond());
				L += " do\n";
				StatementToLua(t->Body());
				L += "end\n";
			}
			else if (x->Is<Lambda>()) {
				Error("todo");
			}
			else if (x->Is<Return>()) {
				Return* t = x->As<Return>();
				L += "do return ";
				ExprToLua(t->GetVal());
				L += " end\n";
			}
			else if (x->Is<For>()) {
				For* t = x->As<For>();
				String s = t->GetNameStr();
				L += "do\n";
				L += "local " + s + " = ";
				ExprToLua(t->Init());
				L += "\nwhile ";
				ExprToLua(t->Inv());
				L += " do\n";
				StatementToLua(t->Body());
				L += s + " = ";
				ExprToLua(t->Next());
				L += "\nend\n";
				L += "end\n";
			}
			else if (x->Is<ForEach>()) {
				ForEach* t = x->As<ForEach>();
				String s = t->GetNameStr();
				L += "for _ignore_, ";
				L += s;
				L += " in makeiter(";
				ExprToLua(t->List());
				L += ") do\n";
				StatementToLua(t->Body());
				L += "end\n";
			}		
			else if (x->Is<Var>()) {
				Var* t = x->As<Var>();
				String s = t->GetNameStr();
				L += "local ";
				L += s;
				if (t->GetVal() != NULL) {
					L += " = ";
					ExprToLua(t->GetVal());
				}
				L += "\n";
			}
			else if (x->Is<Seq>()) {
				Seq* t = x->As<Seq>();
				L += "do\n";
					for (int i=0; i < t->GetNumChildren(); ++i) {
					StatementToLua(t->GetChild(i));
				}
				L += "end\n";
			}
			else {
				ExprToLua(x);
				L += "\n";
			}
		}
		void FormalsToLua(Formals* x) {
			L += "( ";
			for (int i=0; i < x->GetNumChildren(); ++i) {
				if (i != 0) L += ", ";
				Formal* t = x->GetChild(i)->As<Formal>();
				String s = t->GetNameStr();
				L += s;
			}
			L += ")";
		}

		void DefineToLua(Define* x) {
			L += "function ";
			Define* t = x->As<Define>();
			String s = x->GetNameStr();
			L += s;
			FormalsToLua(x->GetFormals());
			L += "\n";
			StatementToLua(x->Body());
			L += "end\n";
		}

		void OutputCommonHeader() {
			// TODO: get this from the directory path
			L += "dofile 'C:\\\\cygwin\\\\home\\\\Chr15topher\\\\dev\\\\cvml\\\\HeronI\\\\heron_lua_adapter.lua'\n";
			//L += "dofile '..\\\\heron_lua_adapter.lua'\n";
		}

		void FieldToLua(Field* f) {
			L += f->GetNameStr();
			L += " = ";
			if (f->GetTypeStr() == "Int") {
				L += "0";
			}
			else if (f->GetTypeStr() == "Float") {
				L += "0.0";
			}
			else if (f->GetTypeStr() == "Bool") {
				L += "false";
			}
			else if (f->GetTypeStr() == "Char") {
				L += "\'0\'";
			}				
			else if (f->GetTypeStr() == "String") {
				L += "\"\"";
			}
			else {
				L += "{}\n";
			}
		}

		void MakeClassInitializer(Class* c) {
			L += "function ";
			L += c->GetNameStr();
			L += ":__init()";
			L += "\n";
			L += "self = {\n";			
			Fields* f = c->GetFields();
			for (int i=0; i < f->GetNumChildren(); ++i ) {
				FieldToLua(f->GetChild(i)->As<Field>());
			}
			L += "}\n";
			L += "end\n";
		}

		void CreateConstructor(Class* c, Method* m) {
			L += "function ";
			L += c->GetNameStr();
			L += ":New";
			FormalsToLua(m->GetFormals());
			L += "\n";
			L += "local result = ";
			L += c->GetNameStr();
			L += "()\n";
			L += "result:Constructor";
			FormalsToLua(m->GetFormals());
			L += "\n";
			L += "return result\n";
			L += "end\n";
		}


		void MethodToLua(Class* c, Method* m) {
			L += "function ";
			L += c->GetNameStr();
			L += ":";
			L += m->GetNameStr();
			FormalsToLua(m->GetFormals());
			L += "\n";
			StatementToLua(m->GetBody());
			L += "end\n";

			if (m->GetNameStr() == "Constructor") {
				CreateConstructor(c, m);
			}
		}

		void MethodsToLua(Class* c, Methods* ms) {
			for (int i=0; i < ms->GetNumChildren(); ++i) {
				Method* m = ms->GetChild(i)->As<Method>();
				MethodToLua(c, m);
			}
		}
		
		bool IsDefaultConstructorDeclared(Methods* ms) {
			for (int i=0; i < ms->GetNumChildren(); ++i) {
				Method* m = ms->GetChild(i)->As<Method>();
				if (m->GetNameStr() == "Constructor" &&
					m->GetFormals()->GetNumChildren() == 0)
					return true;
			}
			return false;
		}

		void ClassToLua(Class* x) {
			// TODO: create a constructor.
			// TODO: provide a clone function
			// TODO: provide an event stack
			// TODO: provide a finalize
			// TODO: deal with inheritance
			// TODO: deal with methods
			// TODO: deal with fields

			L += x->GetNameStr();
			L += " = class()\n\n";
			MethodsToLua(x, x->GetMethods());

			MakeClassInitializer(x);
			
			if (!IsDefaultConstructorDeclared(x->GetMethods())) {
				L += x->GetNameStr();
				L += ":Constructor()\nend\n";
				L += x->GetNameStr();
				L += ":New()\n";
				L += "return ";
				L += x->GetNameStr();
				L += "()\n";
				L += "end\n";
			}
		}	
			
		void ModuleToLua(Module* x) {	
			OutputCommonHeader();
			for (int i=1; i < x->GetNumChildren(); ++i) {
				SExpr* t = x->GetChild(i);
				Class* c = t->As<Class>();
				Assert(c != NULL, "modules can only have classes as children");
				ClassToLua(c);
			}
			L += "Main.New()\n";

			/*
			TODO: eventually support top-level statements again.
			for (int i=0; i < x->GetStatements()->GetNumChildren(); ++i) {
				SExpr* t = x->GetStatements()->GetChild(i);
				Define* def = t->As<Define>();
				Assert(def != NULL, "only defines are handled at the top level");
				DefineToLua(def);
			}
			*/
		}
	};
}

#endif
